const fs = require('fs');
const path = require('path');
const { Parser } = require('m3u8-parser');
const got = require('got');
const crypto = require('crypto');
const download = require('download');
const ffmpeg = require('fluent-ffmpeg');
const ffmpegPath = require('ffmpeg-static').replace('app.asar', 'app.asar.unpacked');
const { Readable } = require('stream');
const async = require('async');
const sleep = require('util').promisify(setTimeout);
const { BaseDownloader } = require('./BaseDownloader');
const UserStopError = require('./errors/UserStopError');
const { DownloadStatus } = require('../../common/DownloadStatus');
const { stringify } = require('../../common/json');

class FFmpegStreamReadable extends Readable {
    constructor(opt) {
        super(opt);
    }
    _read() { }
}

class M3U8Downloader extends BaseDownloader {
    constructor(options) {
        super(options);
        this.segments = [];
        this.keyIV = options.keyIV || '';
        this.deleteSegments = options.deleteSegments || false;
        this.concurrentDownloads = options.concurrentDownloads || 5;
        this.downloadedCount = 0;
        this._tempDir = path.join(this.dir, this.taskName);
        this._tempFiles = {
            key: '',
            dl: [],
            ts: [],
        };
    }

    ensureDownloadDir() {
        super.ensureDownloadDir();
        if (!fs.existsSync(this._tempDir)) {
             fs.mkdirSync(this._tempDir, { recursive: true });
        }
    }

    async start() {
        try {
            this.status = DownloadStatus.Preparing;
            this.emitProgress({
                status: 'preparing',
                message: 'Initializing download...',
                stage: 'start'
            });
            this.emit('download-started');
            this.ensureDownloadDir();
            
            // Parse M3U8
            this.emitProgress({
                status: 'parsing',
                message: 'Parsing M3U8 file...',
                stage: 'parse'
            });
            this.emit('parse-started');
            const parser = await this.wrapAsyncTaskWithStopCheck(this.parseM3U8)();
            this.segments = parser.manifest.segments;
            this.emit('parse-completed', {
                segmentCount: this.segments.length,
                duration: this.segments.reduce((sum, seg) => sum + seg.duration, 0)
            });
            
            // Download segments
            await this.wrapAsyncTaskWithStopCheck(this.downloadSegments)();
            
            // Merge segments
            const filePath = await this.wrapAsyncTaskWithStopCheck(this.mergeSegments)();
            
            this.emitComplete(filePath);
        } catch (err) {
            if (err instanceof UserStopError) {
                console.debug('用户停止下载');
                this.emit('stopped');
            } else {
                console.error('下载失败', err);
                this.emit('error', {
                    stage: this.status.toLowerCase(),
                    error: err,
                    message: `Download failed during ${this.status.toLowerCase()} stage`
                });
                this.emitError(err);
            }
        }
    }

    async parseM3U8() {
        let m3u8Content = '';
        let parser = new Parser();
        
        if (/^file:\/\/\//g.test(this.url)) {
            m3u8Content = fs.readFileSync(this.url.replace(/^file:\/\/\//, ''), { encoding: 'utf-8' });
            parser.push(m3u8Content);
            parser.end();
        } else {
            for (let i = 0; i < 3; i++) {
                const response = await got(this.url, {
                    headers: this.headers,
                    timeout: 30000,
                    rejectUnauthorized: false
                });
                
                if (response.body) {
                    parser.push(response.body);
                    parser.end();
                    break;
                }
            }
        }
        
        return parser;
    }

    async downloadSegments() {
        this.status = DownloadStatus.Downloading;
        this.downloadedCount = 0;
        this.emitProgress({
            status: 'downloading',
            message: 'Starting segment downloads...',
            current: 0,
            total: this.segments.length,
            stage: 'download'
        });
        this.emit('download-segments-started', {
            totalSegments: this.segments.length,
            concurrentDownloads: this.concurrentDownloads
        });

        const queue = async.queue(async ({ index, segment }) => {
            try {
                await this.wrapAsyncTaskWithStopCheck(this.downloadSegment)(segment, index);
                this.downloadedCount++;
                this.emitProgress({
                    status: 'downloading',
                    current: this.downloadedCount,
                    total: this.segments.length
                });
            } catch (err) {
                // queue.kill 之后无法触发 drain，即使抛出异常也无法触发 error
                if (err instanceof UserStopError) {
                    console.debug(`用户停止下载，队列剩余：${queue.length()}`);
                } else {
                    console.error('片段下载异常:', segment.uri, err);
                    this.emit('segment-error', {
                        segment,
                        error: err
                    });
                    this.emitError(err);
                }
                throw err;
            }
        }, this.concurrentDownloads);

        return new Promise((resolve, reject) => {
            queue.drain(resolve);
            queue.error(reject);

            this.segments.forEach((segment, index) => {
                queue.push({ index, segment });
            });
        });
    }

    async downloadSegment(segment, index) {
        const partentUri = this.url.replace(/([^\/]*\?.*$)|([^\/]*$)|([^\/]*$)/g, '');
        let tsUri = '';
        
        // Resolve TS file URL
        if (/^http.*/.test(segment.uri)) {
            tsUri = segment.uri;
        } else if (/^http/.test(this.url) && /^\/.*/.test(segment.uri)) {
            const match = this.url.match(/^https?:\/\/[^/]*/);
            tsUri = match?.[0] + segment.uri;
        } else {
            tsUri = partentUri + segment.uri;
        }

        const filename = `${(index + 1 + '').padStart(6, '0')}.ts`;
        const filePath = path.join(this._tempDir, filename);
        const tempPath = path.join(this._tempDir, filename + '.dl');

        // Skip if final TS file already exists
        if (fs.existsSync(filePath)) {
            this._tempFiles.ts.push(filePath);
            return;
        }

        // Skip download if temp file exists and is valid
        if (!fs.existsSync(tempPath) || fs.statSync(tempPath).size <= 0) {
            // Download with retry
            for (let attempt = 0; attempt < 3; attempt++) {
                try {
                    if (/^file:\/\/\//.test(tsUri)) {
                        fs.copyFileSync(tsUri.replace(/^file:\/\/\//, ''), tempPath);
                    } else {
                        await this.wrapAsyncTaskWithStopCheck(download)(tsUri, this._tempDir, {
                            filename: filename + '.dl',
                            timeout: 30000,
                            headers: this.headers,
                            rejectUnauthorized: false
                        });
                    }

                    // Verify download
                    if (!fs.existsSync(tempPath)) {
                        continue;
                    }
                    if (fs.statSync(tempPath).size <= 0) {
                        fs.unlinkSync(tempPath);
                        continue;
                    }

                    // Successful
                    break;
                } catch (err) {
                    if (attempt === 2) throw err;
                }
            }
        }

        // Handle encryption if needed
        if (segment.key?.method) {
            await this.wrapAsyncTaskWithStopCheck(this.decryptSegment)(segment, index, tempPath, filePath);
        } else {
            fs.renameSync(tempPath, filePath);
        }
    }

    async decryptSegment(segment, index, tempPath, outputPath) {
        const algorithm = `${segment.key.method.toLowerCase()}-cbc`;
        let key, iv;
        
        // Get encryption key
        if (this.keyIV) {
            key = Buffer.from(this.keyIV.substr(0, 32), 'hex');
            iv = this.keyIV.length >= 64 
                ? Buffer.from(this.keyIV.substr(this.keyIV.length - 32, 32), 'hex')
                : Buffer.from(index.toString(16).padStart(32, '0'), 'hex');
        } else if (this._key && this._iv) {
            // 取缓存
            key = this._key;
            iv = this._iv;
        } else {
            const keyUri = this.resolveKeyUri(segment.key.uri);
            const keyPath = path.join(this._tempDir, 'aes.key');
            this._tempFiles.key = keyPath;

            if (/^http/.test(keyUri)) {
                await this.wrapAsyncTaskWithStopCheck(download)(keyUri, this._tempDir, {
                    filename: 'aes.key',
                    headers: this.headers,
                    timeout: 30000,
                    rejectUnauthorized: false
                });
            } else if (/^file:\/\/\//.test(keyUri)) {
                fs.copyFileSync(keyUri.replace(/^file:\/\/\//, ''), keyPath);
            }

            key = fs.readFileSync(keyPath);
            if (key.length === 32) {
                key = Buffer.from(key.toString('utf8'), 'hex');
            }
            iv = segment.key.iv 
                ? Buffer.from(segment.key.iv.buffer) 
                : Buffer.from(index.toString(16).padStart(32, '0'), 'hex');
            // 缓存key和iv，避免重复下载
            this._key = key;
            this._iv = iv;
            console.debug('解密参数', algorithm, key.toString(), iv.toString());
        }

        // Decrypt
        let cipher;
        try {
            cipher = crypto.createDecipheriv(algorithm, key, iv);
        } catch (e) {
            console.error('解密异常', algorithm, key.toString(), iv.toString(), index);
            throw e;
        }
        
        const input = fs.readFileSync(tempPath);
        const output = Buffer.concat([cipher.update(input), cipher.final()]);
        fs.writeFileSync(outputPath, output);
        // 保存文件路径，以便后续清理
        this._tempFiles.dl.push(tempPath);
        this._tempFiles.ts.push(outputPath);
    }

    resolveKeyUri(keyUri) {
        if (/^http.*/.test(keyUri)) return keyUri;
        
        const parentUri = this.url.replace(/([^\/]*\?.*$)|([^\/]*$)/g, '');
        if (/^http/.test(this.url) && /^\/.*/.test(keyUri)) {
            const match = this.url.match(/^https?:\/\/[^/]*/);
            return match?.[0] + keyUri;
        } else if (/^file:\/\/\//.test(this.url)) {
            const fileDir = this.url.replace('file:///', '').replace(/[^\\/]{1,}$/, '');
            return "file:///" + path.join(fileDir, keyUri);
        }
        return parentUri + keyUri;
    }

    async mergeSegments() {
        this.status = DownloadStatus.Merging;
        this.emitProgress({
            status: 'merging',
            message: 'Merging segments...',
            stage: 'merge'
        });
        this.emit('merge-started', {
            segmentCount: this.segments.length
        });
        
        // 添加停止监听
        this.on('stop', () => {
            this.status = DownloadStatus.Stopped;
            this.emitProgress({
                status: 'stopped',
                message: 'Merge stopped by user',
                current: this.downloadedCount,
                total: this.segments.length
            });
        });
        
        return new Promise(async (resolve, reject) => {
            const ffmpegInputStream = new FFmpegStreamReadable(null);
            const outputPath = path.join(this.dir, `${this.taskName}.mp4`);
            
            const fileList = this.getConcatFileList();
            const totalFiles = fileList.length;
            
            ffmpeg(ffmpegInputStream)
                .setFfmpegPath(ffmpegPath)
                .videoCodec('copy')
                .audioCodec('copy')
                .format('mp4')
                .save(outputPath)
                .on('progress', info => {
                    // Just log ffmpeg progress info
                    console.log('FFmpeg progress:', stringify(info));
                })
                .on('end', () => {
                    this.cleanup();
                    resolve(outputPath);
                })
                .on('error', err => reject(err));
            
            // Process files and update progress
            for (let i = 0; i < fileList.length; i++) {
                const stream = fs.readFileSync(fileList[i]);
                ffmpegInputStream.push(stream);
                
                // Update progress based on file count
                const percent = Math.min(100, ((i + 1) / totalFiles * 100).toFixed(1));
                this.emitProgress({
                    status: 'merging',
                    progress: percent,
                    message: `Merging progress: ${percent}% (${i + 1}/${totalFiles} files)`
                });
                
                while (ffmpegInputStream._readableState.length > 0) {
                    await sleep(50);
                    if (this.shouldStop()) {
                        return;
                    }
                }
            }
            ffmpegInputStream.push(null);
        });
    }

    getConcatFileList() {
        const fileList = [];
        for (let i = 0; i < this.segments.length; i++) {
            const filename = `${((i + 1) + '').padStart(6, '0')}.ts`;
            const filePath = path.join(this._tempDir, filename);
            
            if (fs.existsSync(filePath)) {
                fileList.push(filePath);
            }
        }
        return fileList;
    }

    cleanup() {
        const { key, dl, ts } = this._tempFiles;
        // Delete key file
        if (key && fs.existsSync(key)) {
            fs.unlinkSync(key);
        }
        // Delete downloaded segments
        dl.forEach(s => {
            if (s && fs.existsSync(s)) {
                fs.unlinkSync(s);
            }
        });
        // Delete TS files if deleteSegments is true
        if (this.deleteSegments) {
            ts.forEach(s => {
                if (s && fs.existsSync(s)) {
                    fs.unlinkSync(s);
                }
            });
        }
        // Delete temp directory if empty
        const files = fs.readdirSync(this._tempDir);
        if (files.length === 0) {
            fs.rmdirSync(this._tempDir);
        }
    }
}

module.exports = M3U8Downloader;
